Skip to main content

Overview

NAT (Network Address Translation) gateways enable instances in private subnets to access the internet while preventing inbound connections from the internet. The module supports two NAT gateway deployment modes:
  • Multiple NAT Gateways - One NAT gateway per availability zone (high availability)
  • Single NAT Gateway - One shared NAT gateway across all AZs (cost-optimized)

Configuration Variables

enable_nat_gateway
bool
Controls whether NAT gateways should be provisioned for private networks.Default: falseDefined in: variables.tf:58-61
single_nat_gateway
bool
Controls whether to provision a single shared NAT gateway across all private networks instead of one per AZ.Default: falseDefined in: variables.tf:63-66
NAT gateways require public subnets to exist, as they must be deployed in public subnets with internet gateway access.

How NAT Gateways Work

Elastic IP Allocation

From main.tf:111-115, Elastic IPs are allocated for NAT gateways:
resource "aws_eip" "nateip" {
  count = "${var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : 0}"

  vpc = true
}
The count logic:
  • If enable_nat_gateway = false: 0 EIPs created
  • If single_nat_gateway = true: 1 EIP created
  • If single_nat_gateway = false: EIPs created equal to number of AZs

NAT Gateway Creation

From main.tf:117-124, NAT gateways are created in public subnets:
resource "aws_nat_gateway" "natgw" {
  count = "${var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : 0}"

  allocation_id = "${element(aws_eip.nateip.*.id, (var.single_nat_gateway ? 0 : count.index))}"
  subnet_id     = "${element(aws_subnet.public.*.id, (var.single_nat_gateway ? 0 : count.index))}"

  depends_on = ["aws_internet_gateway.mod"]
}

Private Route Configuration

From main.tf:35-41, routes to NAT gateways are added to private route tables:
resource "aws_route" "private_nat_gateway" {
  count = "${var.enable_nat_gateway ? length(var.azs) : 0}"

  route_table_id         = "${element(aws_route_table.private.*.id, count.index)}"
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = "${element(aws_nat_gateway.natgw.*.id, count.index)}"
}

Deployment Modes

Architecture Diagrams

Multiple NAT Gateways Architecture

┌─────────────────────────────────────────────────────────────┐
│                           VPC                                │
│                       10.0.0.0/16                            │
│                                                              │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐   │
│  │   us-east-1a  │  │   us-east-1b  │  │   us-east-1c  │   │
│  │               │  │               │  │               │   │
│  │ Public Subnet │  │ Public Subnet │  │ Public Subnet │   │
│  │ 10.0.101.0/24 │  │ 10.0.102.0/24 │  │ 10.0.103.0/24 │   │
│  │      │        │  │      │        │  │      │        │   │
│  │   NAT GW 1    │  │   NAT GW 2    │  │   NAT GW 3    │   │
│  │      │        │  │      │        │  │      │        │   │
│  └──────┼────────┘  └──────┼────────┘  └──────┼────────┘   │
│         │                  │                  │             │
│  ┌──────▼────────┐  ┌──────▼────────┐  ┌──────▼────────┐   │
│  │               │  │               │  │               │   │
│  │Private Subnet │  │Private Subnet │  │Private Subnet │   │
│  │  10.0.1.0/24  │  │  10.0.2.0/24  │  │  10.0.3.0/24  │   │
│  └───────────────┘  └───────────────┘  └───────────────┘   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Single NAT Gateway Architecture

┌─────────────────────────────────────────────────────────────┐
│                           VPC                                │
│                       10.0.0.0/16                            │
│                                                              │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐   │
│  │   us-east-1a  │  │   us-east-1b  │  │   us-east-1c  │   │
│  │               │  │               │  │               │   │
│  │ Public Subnet │  │ Public Subnet │  │ Public Subnet │   │
│  │ 10.0.101.0/24 │  │ 10.0.102.0/24 │  │ 10.0.103.0/24 │   │
│  │      │        │  │               │  │               │   │
│  │   NAT GW      │  │               │  │               │   │
│  │      │        │  │               │  │               │   │
│  └──────┼────────┘  └───────────────┘  └───────────────┘   │
│         │                  │                  │             │
│  ┌──────▼────────┐  ┌──────▼────────┐  ┌──────▼────────┐   │
│  │       └───────┼──┼───────┘       └──┼───────┘       │   │
│  │Private Subnet │  │Private Subnet │  │Private Subnet │   │
│  │  10.0.1.0/24  │  │  10.0.2.0/24  │  │  10.0.3.0/24  │   │
│  └───────────────┘  └───────────────┘  └───────────────┘   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Use Cases

Production Environments

module "vpc" {
  source = "github.com/terraform-community-modules/tf_aws_vpc"

  name = "production-vpc"
  cidr = "10.0.0.0/16"
  
  azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
  
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
  
  # High availability NAT configuration
  enable_nat_gateway = true
  single_nat_gateway = false  # One NAT gateway per AZ
  
  tags = {
    Environment = "production"
    Criticality = "high"
  }
}

Development/Testing Environments

module "vpc" {
  source = "github.com/terraform-community-modules/tf_aws_vpc"

  name = "dev-vpc"
  cidr = "10.1.0.0/16"
  
  azs = ["us-west-2a", "us-west-2b"]
  
  private_subnets = ["10.1.1.0/24", "10.1.2.0/24"]
  public_subnets  = ["10.1.101.0/24", "10.1.102.0/24"]
  
  # Cost-optimized NAT configuration
  enable_nat_gateway = true
  single_nat_gateway = true  # Single shared NAT gateway
  
  tags = {
    Environment = "development"
    Criticality = "low"
  }
}

Secure Database Tier

module "vpc" {
  source = "github.com/terraform-community-modules/tf_aws_vpc"

  name = "secure-database-vpc"
  cidr = "10.2.0.0/16"
  
  azs = ["eu-central-1a", "eu-central-1b", "eu-central-1c"]
  
  database_subnets = ["10.2.21.0/24", "10.2.22.0/24", "10.2.23.0/24"]
  
  # No NAT gateway - databases are fully isolated
  enable_nat_gateway = false
  
  # Use VPC endpoints for AWS service access
  enable_s3_endpoint = true
  
  create_database_subnet_group = true
  
  tags = {
    Environment = "production"
    Tier        = "database"
  }
}

Cost Considerations

NAT Gateway Pricing (as of 2024):
  • Hourly charge: ~$0.045 per NAT gateway per hour
  • Data processing: ~$0.045 per GB processed
  • Monthly cost per NAT gateway: ~$32.40 (hourly) + data processing
Example Monthly Costs:
  • Single NAT gateway: ~$32-50/month
  • Three NAT gateways: ~$97-150/month
  • Data transfer costs vary by usage

Cost Optimization Strategies

  1. Use Single NAT Gateway for non-production environments
  2. Leverage VPC Endpoints to avoid data processing charges for S3/DynamoDB traffic
  3. Consider NAT Instances for very low-traffic scenarios (not supported by this module)
  4. Monitor Data Transfer to identify optimization opportunities

Best Practices

Production Recommendations:
  • ✓ Use single_nat_gateway = false for high availability
  • ✓ Deploy NAT gateways in all availability zones
  • ✓ Monitor NAT gateway metrics (BytesOutToDestination, BytesInFromSource)
  • ✓ Use VPC endpoints to reduce NAT gateway data processing costs
Common Mistakes:
  • Enabling NAT gateway without public subnets (deployment fails)
  • Using single NAT gateway in production (single point of failure)
  • Not considering data processing costs for high-traffic workloads
  • Forgetting that database and ElastiCache subnets share private route tables

Troubleshooting

Private Instances Can’t Access Internet

Check:
  1. enable_nat_gateway = true is set
  2. Public subnets exist and have internet gateway routes
  3. Private subnet route tables have routes to NAT gateway (main.tf:35-41)
  4. Security groups allow outbound traffic
  5. Network ACLs allow outbound traffic

High NAT Gateway Costs

Solutions:
  1. Enable S3 VPC endpoint to avoid S3 traffic through NAT gateway
  2. Enable DynamoDB VPC endpoint for DynamoDB traffic
  3. Review CloudWatch metrics to identify high-traffic sources
  4. Consider AWS PrivateLink for other AWS services

Single NAT Gateway Failure

Impact:
  • All private subnets lose internet access
  • Cross-AZ data transfer charges for subnets in other AZs
Solution:
# Upgrade to high availability
single_nat_gateway = false

Subnet Types

Configure private, public, and database subnets

VPC Endpoints

Reduce NAT gateway costs with VPC endpoints